#include"epollServer.hpp"
#include<memory>

using namespace std;
using namespace epoll_ns;

static void usage(std::string proc)
{
    std::cerr << "usage:\t\n" << proc << "port" << std::endl;
}

std::string echo(const std::string& message)
{
    return "I am epollserver# " + message;
}

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<EpollServer> epollsvr(new EpollServer(echo,port));

    epollsvr->initServer();

    epollsvr->start();
    return 0;
}


///////////////////////////////////////////////系统调用接口//////////////////////////////////////////////

// int epoll_create(int size);
// 创建一个epoll的句柄.
// 自从linux2.6.8之后，size参数是被忽略的. 
// 用完之后, 必须调用close()关闭.

// int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// epoll的事件注册函数.
// 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型. 
// 第一个参数是epoll_create()的返回值(epoll的句柄).
// 第二个参数表示动作，用三个宏来表示. 
// 第三个参数是需要监听的fd. 
// 第四个参数是告诉内核需要监听什么事. 
// 第二个参数的取值:
// EPOLL_CTL_ADD ：注册新的fd到epfd中；
// EPOLL_CTL_MOD ：修改已经注册的fd的监听事件；
// EPOLL_CTL_DEL ：从epfd中删除一个fd；
// struct epoll_event结构如下:
// 有epoll_data 和 epoll_event

// int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);//和select/poll一模一样
// 收集在epoll监控的事件中已经发送的事件.  
// 参数events是分配好的epoll_event结构体数组. 
// epoll将会把发生的事件赋值到events数组中 (events不可以是空指针，内核只负责把数据复制到这个
// events数组中，不会去帮助我们在用户态中分配内存). 
// maxevents告之内核这个events有多大，这个 maxevents的值不能大于创建epoll_create()时的size. 
// 参数timeout是超时时间 (毫秒，0会立即返回，-1是永久阻塞). 
// 如果函数调用成功，返回对应I/O上已准备好的文件描述符数目，如返回0表示已超时, 返回小于0表示函
// 数失败. 


///////////////////////////////////////////////底层原理//////////////////////////////////////////////////


// 当某一进程调用epoll_create方法时，Linux内核会创建一个eventpoll结构体，这个结构体中有两个成员与epoll的使用方式密切相关. 
// struct eventpoll{ 
//  .... 
//  /*红黑树的根节点，这颗树中存储着所有添加到epoll中的需要监控的事件*/ 
//  struct rb_root rbr; 
//  /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ 
//  struct list_head rdlist; 
//  .... 
// }; 

// 每一个epoll对象都有一个独立的eventpoll结构体，用于存放通过epoll_ctl方法向epoll对象中添加进来的事件.
// 这些事件都会挂载在红黑树中，如此，重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn，其中n为树的高度).
// 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系，也就是说，当响应的事件发生时会调用这个回调方法.
// 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中. 
// 在epoll中，对于每一个事件，都会建立一个epitem结构体.

// struct epitem{ 
//  struct rb_node rbn;//红黑树节点 
//  struct list_head rdllink;//双向链表节点 
//  struct epoll_filefd ffd; //事件句柄信息 
//  struct eventpoll *ep; //指向其所属的eventpoll对象 
//  struct epoll_event event; //期待发生的事件类型 
// }
// 当调用epoll_wait检查是否有事件发生时，只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可. 
// 如果rdlist不为空，则把发生的事件复制到用户态，同时将事件数量返回给用户. 这个操作的时间复杂度是O(1).
// 总结一下, epoll的使用过程就是三部曲:
// 调用epoll_create创建一个epoll句柄;
// 调用epoll_ctl, 将要监控的文件描述符进行注册;
// 调用epoll_wait, 等待文件描述符就绪


///////////////////////////////////////////////重新回归epoll原理，LT,ET//////////////////////////////////////////////////

// a.什么叫做事件就绪？ 底层IO条件满足了，可以进行某种IO行为了，就叫做事件就绪
    // select/poll/epool -> 等 -> IO就绪事件的通知机制
    // 通知机制又没策略呢？
    // epoll的不同的工作模式！ -> 本质：通知用户事件来到！！！
    // 水平触发 LT(Level Triggered)工作模式：只要地城数据没读完，epoll就会一直通知用户要读取数据  --> 默认的工作模式
    // 边缘触发 ET(Edge Triggered)工作模式：只要地城数据没读完，epoll 不在通知用户，除非底层的数据变化的时候(再次增多)，才会再通知你一次！、

// ET：底层只有数据从无到有，从有到多 变化的时候，才会通知(rb+cbreadyqueue)上层 -> 只会通知一次 ->
// 倒逼程序员将本轮就绪的数据全部读取到上层->你怎么知道你把本次就绪的底层的数据读取完毕了呢? 循环读取，知道读取不到数据了！->
// 一般的fd，是阻塞式的fd -> ET,对应的fd必须是非阻塞的！！！

// LT，阻塞的，非阻塞也可以 --> LT模式下，我也可以防止ET的工作模式！！
// ET为什么更高效？
// 1.不仅仅体现在通知机制上(倒逼程序员将本轮就绪的数据全部读取到上层)
// 2.尽快让上层把数据取走->TCP 可以给发送对方听过一个更大的窗口大小->让对方更新出更大的滑动窗口->提供底层的数发送效率，更好的理由诸如TCP延迟应答等策略！！

// TCP 中 PSH的作用？ 让底层数据就绪事件。再让上层知道！